package com.iakuil.app.service.base;

import com.alicp.jetcache.Cache;
import com.alicp.jetcache.CacheManager;
import com.alicp.jetcache.anno.CacheType;
import com.alicp.jetcache.template.QuickConfig;
import com.iakuil.app.common.core.domain.BaseEntity;
import com.iakuil.app.repository.base.CrudMapper;
import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;

import java.time.Duration;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 带简单缓存的通用Service实现
 *
 * <p>注意：使用ID作为缓存key，入参不带ID的方法无效。
 * <p>比如：{@code #getOne()}方法不会产生缓存，{@code listByIds()}方法可以产生缓存，再次调用仍然会查询数据库。
 * <p>经测试，空值缓存不生效，复杂业务请自行配置缓存策略。
 *
 * @author Kai
 * @date 2022/7/7 19:26
 **/
public abstract class CacheableServiceImpl<M extends CrudMapper<T>, T extends BaseEntity> extends BaseServiceImpl<M, T> {

    @Autowired
    protected CacheManager cacheManager;

    protected Cache<Long, T> entityCache;

    @PostConstruct
    public void init() {
        // 默认缓存一分钟，最多一万个实体
        QuickConfig qc = QuickConfig.newBuilder(entityClass.getSimpleName() + "Cache")
            .expire(Duration.ofSeconds(60))
            .cacheType(CacheType.BOTH)
            .localLimit(10000)
            .syncLocal(true)
            .build();
        entityCache = cacheManager.getOrCreateCache(qc);
    }

    @Override
    @Transactional(readOnly = true)
    public T getById(Long id) {
        T target = entityCache.get(id);
        if (target == null) {
            target = super.getById(id);
            if (target != null) {
                entityCache.put(id, target);
            }
        }

        return target;
    }

    @Override
    @Transactional(readOnly = true)
    public List<T> listByIds(Long... ids) {
        List<T> entities = super.listByIds(ids);
        cacheAll(entities);
        return entities;
    }

    @Override
    @Transactional(readOnly = true)
    public List<T> listByIds(Collection<Long> ids) {
        List<T> entities = super.listByIds(ids);
        cacheAll(entities);
        return entities;
    }

    private void cacheAll(List<T> entities) {
        entityCache.putAll(entities.stream().filter(Objects::nonNull).distinct().collect(Collectors.toMap(BaseEntity::getId, v -> v, (v1, v2) -> v2)));
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean modify(T entity) {
        boolean modified = super.modify(entity);
        if (modified) {
            entityCache.remove(entity.getId());
        }
        return modified;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean modifyInBatch(Collection<T> entities) {
        boolean succ = super.modifyInBatch(entities);
        if (succ) {
            entityCache.removeAll(entities.stream().map(T::getId).collect(Collectors.toSet()));
        }
        return false;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean saveOrModify(T entity) {
        boolean modified = super.saveOrModify(entity);
        if (modified) {
            entityCache.remove(entity.getId());
        }
        return modified;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean remove(Long id) {
        boolean removed = super.remove(id);
        if (removed) {
            entityCache.remove(id);
        }
        return removed;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean removeByIds(Long... ids) {
        boolean removed = super.removeByIds(ids);
        if (removed) {
            entityCache.removeAll(Set.of(ids));
        }
        return removed;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean removeByIds(Collection<Long> ids) {
        boolean removed = super.removeByIds(ids);
        if (removed) {
            entityCache.removeAll(Set.copyOf(ids));
        }
        return removed;
    }
}
